/* 
 * File:   EntityParser.cpp
 * Author: RedEyedKiller
 * 
 * Created on 30 Σεπτέμβριος 2010, 4:00 μμ
 */

#include "EntityParser.h"

#include "tinyxml.h"
#include "../AnimationLogic.h"
#include "../Entity.h"
#include "../AnimationLogic.h"
#include "../ResourceManager.h"
#include "../EntityCreator.h"
#include "../depricated/ParticleComponent.h"
#include "../Globals.h"
#include "../Logger.h"
#include "../LuaScriptLogicProto.h"
#include "../Property.h"
#include "../Utilities.h"
#include "IntermidiateParser.h"
#include "ValueParser.h"
#include <algorithm>

namespace FileParser
{

EntityParser::EntityParser(EntityCreator* creator, const std::string& fileExtension) : fileEx(fileExtension)
{
    this->creator = creator;
}

void EntityParser::SetFname(std::string fileExtension)
{
    this->fileEx = fileExtension;
}

EntityParser::~EntityParser()
{
}

void EntityParser::ManageInheritanceList(const std::string& fatherOf)
{
    //if no fatherOf is specified start up a new inheritance list
    //in other case continue the old one
    if( fatherOf.empty( ) )
    {
        inheritanceList.clear( );
    }
    else
    {
        inheritanceList.push_back( fatherOf );
    }
}

bool EntityParser::CheckInheritance(const char* parent, const std::string& key, Entity** entity, bool* shapedData)
{
    if( parent != NULL )
    {
        //if parent is already in the inheritance list it means that its already loaded by a previous entity of the 
        //inheritance list (circular inheritance)
        if( inheritanceList.end( ) != std::find( inheritanceList.begin( ), inheritanceList.end( ), parent ) )
        {
            return false;
        }
        //an entity can't extend its self (y not actually?)
        if( key == parent )
        {
            return false;
        }

        Entity* father = creator->CreateEntity( parent, key );
        //father doesn't exists.
        if( father == NULL )
        {
            return false;
        }

        //replace cached entity with the father so we can build on top of him the new entity.
        delete *entity;
        *entity = father;

        if( ( *entity )->GetPhysicsLogic( )->GetRect( )->CalculateArea( ) >= 1 )
        {
            *shapedData = true;
        }

        return true;
    }

    return true;
}

bool EntityParser::ParseEntity(Entity** entity, const std::string& key, const std::string& fatherOf)
{
    ManageInheritanceList( fatherOf );
    TiXML::TiXmlDocument xFile( ( Globals::GetInstance( )->GetPath( "Entities" ) + ( key + fileEx ) ).c_str( ) );
    if( !xFile.LoadFile( ) )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Error parsing "
                << ( Globals::GetInstance( )->GetPath( "Entities" ) +
                key + fileEx ) << " file not loaded. :" << xFile.ErrorDesc( );
        return false;
    }

    TiXML::TiXmlElement* xEntity = xFile.FirstChildElement( "Entity" );

    bool shapeData = false;

    //valid check 
    if( !CheckInheritance( xEntity->Attribute( "extends" ), key, entity, &shapeData ) )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Error parsing "
                << ( Globals::GetInstance( )->GetPath( "Entities" ) +
                key + fileEx ) << " an inheritance error was found.\n Parent doesn't exist or "
                "a circular reference was detected. " << inheritanceList;
        return false;
    }

    ParseIncludeList(xEntity);
    
    Entity* cacheEntity = *entity;

    //set entity's type
    cacheEntity->SetType( key );
    //parse and set entity's category
    cacheEntity->SetCategory( xEntity->Attribute( "category" ) );
    
    //Parse Logic
    if( !ParseDraw( xEntity->FirstChildElement( "Draw" ), cacheEntity ) )
        return false;
    if( !ParsePhysics( xEntity->FirstChildElement( "Physics" ), cacheEntity, &shapeData ) )
        return false;
    ParseProperties( xEntity->FirstChildElement( "Properties" ), cacheEntity );
    ParseScripts( xEntity->FirstChildElement( "Scripts" ), cacheEntity );

    if( !shapeData && cacheEntity->GetPhysicsLogic( ) != NULL )
    {
        if( cacheEntity->GetAnimationLogic( ) != NULL )
        {
            Math::Vector2I vec = cacheEntity->GetAnimationLogic( )->GetDisplayDimensions( );
            cacheEntity->GetPhysicsLogic( )->GetRect( )->Set( 0.0f, 0.0f, vec.GetY( ) * cacheEntity->GetAnimationLogic( )->GetScale( ).GetY( ),
                                                              vec.GetX( ) * cacheEntity->GetAnimationLogic( )->GetScale( ).GetX( ) );
        }
        else
        {
            cacheEntity->GetPhysicsLogic( )->GetRect( )->Set( 0.0f, 0.0f, 0.0f, 0.0f );
        }
    }
    //clear and out
    xEntity->Clear( );
    xFile.Clear( );
    return true;
}

bool EntityParser::ParseDraw(TiXML::TiXmlElement* xItem, Entity* cacheEntity)
{
    if( !xItem ) return true; //entity doesn't have a draw component.
    TiXML::TiXmlElement *animElement = xItem->FirstChildElement( "AnimationTable" );
    AnimationLogic* draw = NULL;

    //allocate the correct object type and make the correct parsing.
    if( animElement )
    {
        draw = ParseAnimated( xItem, cacheEntity );
    }
    else
    {
	draw = ParseSimpleDraw( xItem, cacheEntity );
    }

    //check if parsing succeded.
    if( draw == NULL ) return false;
    cacheEntity->SetAnimationLogic( draw );

    //clear xml item
    xItem->Clear( );
    return true;
}

AnimationLogic* EntityParser::ParseSimpleDraw(TiXML::TiXmlElement* xItem, Entity* cacheEntity)
{
    //allocate requested Logic component
    AnimationLogic* draw = new AnimationLogic;
    //parse resource file
    TiXML::TiXmlElement* xRes = xItem->FirstChildElement( "Resource" );
    if( xRes == NULL )
    {
        delete draw;
        return NULL;
    }

    std::string texturePath( xRes->Attribute( "fname" ) );

    Resource< gl::Texture > *display = ResourceManager::GetInstance( )->GetTexture( texturePath.c_str( ) );

    if( display == NULL )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Texture " << texturePath << " is missing!\n";
        delete draw;
        return NULL;
    }

    draw->SetDisplay( display );
    xRes->Clear( );

    draw->SetScale( ParseValue< Math::Vector2F > ( xItem->Attribute( "scale" ), Math::Vector2F( 1, 1 ) ) );

    ParseBlending( xItem->FirstChildElement( "Blending" ), draw );

    //simulate one frame animation

    AnimationFrame frame;
    frame.firstFrame.Set( 0, 0, draw->DrawLogic::GetHeight( ), draw->DrawLogic::GetWidth( ) );
    frame.numberOfFrames = 1;
    frame.totalTime = 10;

    frame.flip[0] = ParseValue<bool>( xItem->Attribute( "flipX" ), false );
    frame.flip[1] = ParseValue<bool>( xItem->Attribute( "flipY" ), false );

    draw->AddFrame( "DEF", frame );
    //set animation to play the only one animation frame
    draw->SetDefaultAnimation( "DEF" );
    draw->SetCurrentAnimation( "DEF" );
    draw->SetPlaying( false );
    return draw;
}

void EntityParser::ParseBlending(TiXML::TiXmlElement* xBlending, DrawLogic* draw)
{
    draw->SetColor( ParseValue<gl::Color > ( xBlending->Attribute( "color" ), gl::colors::WHITE ) );
    const char* blend = xBlending->Attribute( "method" );
    if( blend != NULL )
    {
        draw->SetBlending( gl::GetBlendingMethodName( blend ) );
    }
    else
    {
        draw->SetBlending( gl::GetBlendingMethodName( "Alpha" ) );
    }
    xBlending->Clear( );
}

AnimationLogic* EntityParser::ParseAnimated(TiXML::TiXmlElement* xItem, Entity* cacheEntity)
{
    //allocate requested Logic component
    AnimationLogic* anime = new AnimationLogic;
    //parse resource file
    TiXML::TiXmlElement* xRes = xItem->FirstChildElement( "Resource" );
    if( xRes == NULL )
    {
        delete anime;
        return NULL;
    }
    anime->SetDisplay( ResourceManager::GetInstance( )->GetTexture( xRes->Attribute( "fname" ) ) );
    xRes->Clear( );

    anime->SetScale( ParseValue<Math::Vector2F > ( xItem->Attribute( "scale" ), Math::Vector2F( 1, 1 ) ) );

    ParseBlending( xItem->FirstChildElement( "Blending" ), anime );

    xRes = xItem->FirstChildElement( "AnimationTable" );
    std::string str = xRes->Attribute( "default" );
    if( str != "NONE" )
    {
        anime->SetDefaultAnimation( str );
        anime->SetCurrentAnimation( str );
    }

    anime->SetPlaying( ParseValue<bool>( xRes->Attribute( "startPlaying" ), false ) );

    //check if there is any kind of offset in the animation table
    AnimeOffsetStyle offsetStyle = AOS_NONE;
    const char* offset = xRes->Attribute( "offset" );
    if( offset )
    {
        if( !strcmp( offset, "contHeight" ) ) offsetStyle = AOS_HEIGHT;
    }
    //parse animation table
    ParseAnimationTable( xRes->FirstChildElement( "Animation" ), anime, offsetStyle );

    return anime;
}

void EntityParser::ParseAnimationTable(TiXML::TiXmlElement* xAnimations, AnimationLogic* anime, AnimeOffsetStyle offset)
{
    Math::Rect rect;
    rect.Set( 0, 0, 0, 0 );
    while( xAnimations )
    {
        AnimationFrame frame;
        //check if it has a reference in other frames
        const char* ref = xAnimations->Attribute( "as" );
        if( ref )
        {
            if( anime->GetFrame( ref, frame ) )
            {
                //check if there are any overwriting properties
                frame.numberOfFrames = ParseValue<int > ( xAnimations->Attribute( "frames" ), frame.numberOfFrames );
                frame.totalTime = ParseValue<int > ( xAnimations->Attribute( "time" ), frame.totalTime );
            }
            else
            {
                //report error
                LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR ) << "Error parsing " << " " << "AnimationTable has a reference in an undefined frame";
                continue;
            }
        }
        else
        {
            //check if any kind of offset should be calculated
            if( offset == AOS_HEIGHT )
            {
                int off = rect.GetTotalHeight( );
                rect = ParseValue<Math::Rect > ( xAnimations->Attribute( "startRect" ) );
                rect.SetTop( off );
            }
            else if( offset == AOS_NONE )
            {
                rect = ParseValue<Math::Rect > ( xAnimations->Attribute( "startRect" ) );
            }
            frame.firstFrame = rect;
            frame.numberOfFrames = ParseValue<int > ( xAnimations->Attribute( "frames" ), 0 );
            frame.totalTime = ParseValue<int > ( xAnimations->Attribute( "time" ), 0 );
        }

        frame.flip[0] = ParseValue<bool>( xAnimations->Attribute( "flipX" ), false );
        frame.flip[1] = ParseValue<bool>( xAnimations->Attribute( "flipY" ), false );

        anime->AddFrame( xAnimations->Attribute( "name" ), frame );
        xAnimations = Next( xAnimations );
    }
}

bool EntityParser::ParsePhysics(TiXML::TiXmlElement* xItem, Entity* cacheEntity, bool* shapeData)
{
    if( !xItem ) return true; //entity doesn't have a physics component.
    std::string type = xItem->Attribute( "type" );
    PhysicsLogic* phys = NULL;
    if( type == "Simple" )
    {
        phys = ParseSimplePhysics( xItem, cacheEntity, shapeData );
    }
    //check if parsing succeded.
    if( phys == NULL ) return false;
    cacheEntity->SetPhysicsLogic( phys );
    //clear xml item
    xItem->Clear( );
    return true;
}

PhysicsLogic* EntityParser::ParseSimplePhysics(TiXML::TiXmlElement* xItem,
                                               Entity* cacheEntity, bool* shapeData)
{
    //allocate requested Logic component
    PhysicsLogic* phys = new PhysicsLogic;

    TiXML::TiXmlElement* xMass = xItem->FirstChildElement( "Mass" );
    if( xMass == NULL )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR )
                << cacheEntity->GetType( ) << " no mass element in physics was found.";
        delete phys;
        return NULL;
    }

    double mass;
    xMass->Attribute( "value", &mass );
    phys->SetMass( mass );
    xMass->Clear( );

    TiXML::TiXmlElement* xMat = xItem->FirstChildElement( "Material" );
    if( xMat == NULL )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR )
                << cacheEntity->GetType( ) << " no material element in physics was found.";
        delete phys;
        return NULL;
    }

    float bounce = ParseValue<float>( xMat->Attribute( "bounce" ), 0 );
    float friction = ParseValue<float>( xMat->Attribute( "friction" ), 0 );
    phys->SetMaterial( physicsSystem::PhysicsMaterial( friction, bounce ) );
    xMat->Clear( );

    TiXML::TiXmlElement* xDam = xItem->FirstChildElement( "Damping" );
    if( xDam == NULL )
    {
        LOG( Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR )
                << cacheEntity->GetType( ) << " no damping element in physics was found.";
        delete phys;
        return NULL;
    }

    phys->SetDamping( ParseValue<bool>( xDam->Attribute( "value" ), 0 ) );
    phys->ApplyDampingOnX( ParseValue<bool>( xDam->Attribute( "applyonX" ), false ) );
    phys->ApplyDampingOnY( ParseValue<bool>( xDam->Attribute( "applyonY" ), false ) );
    xDam->Clear( );

    phys->SetIgnoreGeneralForces( ParseValue<bool>( xItem->Attribute( "ignoreGeneralForces" ), false ) );
    phys->SetCanSleep( ParseValue<bool>( xItem->Attribute( "canSleep" ), true ) );
    //optional bool ghost
    phys->SetGhost( ParseValue<bool>( xItem->Attribute( "ghost" ), false ) );

    TiXML::TiXmlElement* xRect = xItem->FirstChildElement( "Rect" );
    if( xRect == NULL )
    {
        *shapeData = false;
    }
    else
    {
        *shapeData = true;
        phys->SetRect( ParseValue<Math::Rect > ( xRect->Attribute( "value" ) ) );
        xRect->Clear( );
    }

    return phys;
}

//Parse Script Hierarchy

bool EntityParser::ParseScripts(TiXML::TiXmlElement *scripts, Entity *cacheEntity)
{
    if( scripts )
    {

        TiXML::TiXmlElement *currChild = scripts->FirstChildElement( );

        //for each child
        for(; currChild != NULL; currChild = Next( currChild ) )
        {
            //if the tag is 'Script'
            std::string tagName( currChild->Value( ) );
            if( tagName == "Script" )
            {
                //parse Script tag
                ParseScript( currChild, cacheEntity );
            }
        }

        return true;
    }

    return false;
}

bool EntityParser::ParseScript(TiXML::TiXmlElement *scriptRoot, Entity *cacheEntity)
{
    LuaScriptLogicProto *script = NULL;

    if( scriptRoot->Attribute( "file" ) == NULL )
        return false;

    std::string scriptPath( Globals::GetInstance( )->GetPath( "Behaviours" ) + scriptRoot->Attribute( "file" ) );
    
    try
    {
        script = new LuaScriptLogicProto( scriptPath );
	
	//parse script attributes
        TiXML::TiXmlElement *currScriptChild = scriptRoot->FirstChildElement( );
	
	for(; currScriptChild; currScriptChild = currScriptChild->NextSiblingElement( ) )
	{
	    std::string tagName( currScriptChild->Value( ) );
	    
	    StrToUpper( tagName );
	    
	    if( tagName == "INT" )
	    {
		const char *name = currScriptChild->Attribute( "name" );

		const char *value = currScriptChild->GetText( );
		
		if( name && value )
		{
		    int valueConv = ParseValue<int>( value );
		    
		    script->PushAttribute( name, valueConv );
		}
	    }
	    else if( tagName == "FLOAT" )
	    {
		const char *name = currScriptChild->Attribute( "name" );
		
		const char *value = currScriptChild->GetText( );
		
		if( name && value )
		{
		    float valueConv = ParseValue<float>( value );
		    
		    script->PushAttribute( name, valueConv );
		}
	    }
	    else if( tagName == "STRING" )
	    {
		const char *name = currScriptChild->Attribute( "name" );
		
		const char *value = currScriptChild->GetText( );
	
		if( name && value )
		{
		    script->PushAttribute( name, std::string( value ) );
		}
	    }
	    else if( tagName == "VECTOR2F" )
	    {
		const char *name = currScriptChild->Attribute( "name" );
		
                TiXML::TiXmlElement *xElement = NULL, *yElement = NULL;
		
		xElement = currScriptChild->FirstChildElement( "x" );
		yElement = currScriptChild->FirstChildElement( "y" );
		
		if( xElement && yElement )
		{
		    const char* xTxt = xElement->GetText( );
		    const char* yTxt = yElement->GetText( );
		    
		    if( xTxt && yTxt )
		    {
		        int x = ParseValue<int>( xTxt );
		        int y = ParseValue<int>( yTxt );
		    
		        script->PushAttribute( name, Vector2F( x, y ) );
		    }
		}
	    }
	}
    }
    catch( std::string& exception )
    {
        return false;
    }

    cacheEntity->AddScriptLogic( script );

    return true;
}

bool EntityParser::ParseProperties(TiXML::TiXmlElement *propertyRoot, Entity *cacheEntity)
{
    if( !propertyRoot )
	return false;
    
    TiXML::TiXmlElement *currProperty = propertyRoot->FirstChildElement( );
    
    for( ; currProperty; currProperty = currProperty->NextSiblingElement( ) )
    {
	std::string tag( currProperty->Value( ) );
	
	StrToUpper( tag );
	
	const char *name = currProperty->Attribute( "name" );
	
	if( tag == "INT" )
	{
	    const char *value = currProperty->GetText( );
	    
	    if( name && value )
	    {
		int valueConv = ParseValue<int>( value );
		
		Property *p = cacheEntity->AddProperty( name );
		
		p->StoreInt( valueConv );
	    }
	}
	else if( tag == "FLOAT" )
	{	    
	    const char *value = currProperty->GetText( );
	    
	    if( name && value )
	    {
		float valueConv = ParseValue<float>( value );
		
		Property *p = cacheEntity->AddProperty( name );
		
		p->StoreFloat( valueConv );
	    }
	}
	else if( tag == "BOOL" || tag == "BOOLEAN" )
	{
	    const char *value = currProperty->GetText( );
	    
	    if( name && value )
	    {
		//bool valueConv = ParseValue<bool>( value );
	    
		//Property *p = cacheEntity->AddProperty( name );
		
		//p->StoreBool( valueConv );
	    }
	}
	else if( tag == "STRING" )
	{
	    const char *value = currProperty->GetText( );
	    
	    if( name && value )
	    {
		Property *p = cacheEntity->AddProperty( name );
		
		p->StoreString( value );
	    }
	}
	else if( tag == "VECTOR2F" )
	{
	    if( name )
	    {
		TiXML::TiXmlElement *xElement = NULL, *yElement = NULL;
		
		xElement = currProperty->FirstChildElement( "x" );
		yElement = currProperty->FirstChildElement( "y" );
		
		if( xElement && yElement )
		{
		    const char *xValue = xElement->GetText( );
		    const char *yValue = yElement->GetText( );
		    
		    if( xValue && yValue )
		    {
		        float x = ParseValue<float>( xValue );
			float y = ParseValue<float>( yValue );
			
			Property *p = cacheEntity->AddProperty( name );
			
			p->StoreVector( Math::Vector2F( x, y ) );
		    }
		}
	    }
	}
    }
    
    return true;
}

}
